Skip to content

Generate Spring Security wiring through the beans DSL - #16119

Open
codeconsole wants to merge 3 commits into
apache:8.0.xfrom
codeconsole:forge-security-beans-dsl
Open

Generate Spring Security wiring through the beans DSL#16119
codeconsole wants to merge 3 commits into
apache:8.0.xfrom
codeconsole:forge-security-beans-dsl

Conversation

@codeconsole

@codeconsole codeconsole commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

What

Two changes to the spring-boot-starter-security forge feature.

1. Wire the security beans through the beans DSL

The feature generated a separate SecurityConfig class and pulled it into the generated app with @Import(SecurityConfig). This declares the same two beans through the beans DSL (#16019) on the generated Application class instead, and deletes the SecurityConfig template.

No dependency declaration is added to the generated build: grails-core already exports grails-beans-dsl as an api dependency, so the transform reaches any app that depends on grails-core.

Applications generated without the feature are unaffected — application.rocker.raw renders byte-identical output for them.

2. Lock down the generated defaults

The generated filter chain used anyRequest().permitAll(). That left the scaffolded user admin at /user/** open to anonymous visitors, and made an app generated with the security feature less restrictive than one generated with no security configuration at all, since Spring Boot's own default authenticates everything.

The new rules match the shape the Grails Spring Security feature already configures through staticRules, so the two security features no longer disagree about whether a generated app is open or closed. ROLE_ADMIN is what bootStrap.rocker.raw seeds.

Two deliberate departures from that feature's list: /shutdown is not permitted, and /**/js/**, /**/css/**, /**/images/** and /**/favicon.ico are left out because asset-pipeline serves all of them under /assets/**.

Result

A generated secured app, with no src/main/groovy/**/SecurityConfig.groovy:

@CompileStatic
@EnableWebSecurity
class Application extends GrailsAutoConfiguration {
    static void main(String[] args) {
        GrailsApp.run(Application, args)
    }

    def beans = {

        bean(PasswordEncoder) {
            PasswordEncoderFactories.createDelegatingPasswordEncoder()
        }

        bean('filterChain', SecurityFilterChain) { HttpSecurity http ->
            http
                .authorizeHttpRequests {
                    it.requestMatchers('/', '/index', '/index.gsp', '/error', '/assets/**').permitAll()
                      .requestMatchers('/user/**').hasRole('ADMIN')
                      .anyRequest().authenticated()
                }
                .formLogin { }
                .logout { it.logoutSuccessUrl('/') }
            http.build()
        }
    }
}

Also

user.rocker.raw marks the password field with the password constraint, so scaffolding renders it masked rather than as plain text:

password blank: false, password: true

Testing

SpringBootStarterSecuritySpec asserts the DSL in Application.groovy, that no SecurityConfig.groovy is generated, the new authorization rules, and the new constraint. The negative case asserts neither def beans = { nor @EnableWebSecurity appears without the feature.

  • Full :grails-forge-core:test — 322 tests, 0 failures, 1 skipped

Rendered output was inspected directly for both the secured and unsecured variants.

Not covered here: the tests assert on generated text; no generated project was compiled and booted. That is worth a reviewer's attention on the second change in particular — a lockdown default can break a generated starter on first run in a way permitAll could not, if some path the welcome page needs is missing from the permit list.

The spring-boot-starter-security feature generated a separate
SecurityConfig class and pulled it into the application with
@import(SecurityConfig). Declare the same two beans through the beans
DSL on the generated Application class instead: one fewer file, and the
security wiring sits where an application's other bean wiring goes.

grails-core exports grails-beans-dsl as an api dependency, so a
generated application needs no declaration of its own for the DSL.

Also mark the User domain's password field with the password constraint
so scaffolding masks it.
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.3435%. Comparing base (a1e526f) to head (7fbe45e).
⚠️ Report is 19 commits behind head on 8.0.x.

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##                8.0.x     #16119        +/-   ##
==================================================
+ Coverage     52.3431%   52.3435%   +0.0004%     
- Complexity      18296      18302         +6     
==================================================
  Files            2036       2036                
  Lines           96347      96373        +26     
  Branches        16829      16836         +7     
==================================================
+ Hits            50431      50445        +14     
- Misses          38492      38502        +10     
- Partials         7424       7426         +2     

see 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The generated filter chain permitted every request. That left the
scaffolded user admin at /user/** open to anonymous visitors, and made
an app generated with the security feature less restrictive than one
generated with no security configuration at all, since Spring Boot's
own default authenticates everything.

Permit the home page, the error page and static assets, require
ROLE_ADMIN for /user/**, and authenticate everything else - the same
shape the Grails Spring Security feature already configures through
staticRules, so the two security features no longer disagree about
whether a generated app is open or closed.
BootStrap seeded a single admin user, so every account in a freshly
generated app held ROLE_ADMIN and nothing exercised the distinction the
generated URL rules draw between ROLE_ADMIN and an ordinary logged-in
user. Seed a second ROLE_USER account alongside it.

The delegating password encoder moves to a local so both accounts share
one instance.
@testlens-app

testlens-app Bot commented Aug 9, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 7fbe45e
▶️ Tests: 59145 executed
⚪️ Checks: 60/60 completed


Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants